今天我們將了解如何將靜態的gt
表格呈現於FastHTML app中,並編寫裝飾器來協助表格呈現。
明天則會編寫一個可以動態改變gt
表格背景顏色的FastHTML app,並使用pytest與Playwright進行測試。
今明兩天的內容參考自官方Solar Zenith Angles範例。
fast_app
instance建立一個名為app
的fast_app
instance及一個rt
object。
# https://github.com/jrycw/gt-fasthtml/blob/master/main.py
from functools import cache
import polars as pl
from fasthtml.common import NotStr, Title, Titled, fast_app
from great_tables import GT, html
from great_tables.data import sza
app, rt = fast_app()
其中rt
是FastHTML提供的快速路由功能,其可以偵測所裝飾的函數名,是否為get
、post
等http操作,進而添加路由功能。舉例來說,將@app.get("/")
及將@rt("/")
裝飾在get()
函數上,對FastHTML來說是等義的操作。
get_sza()
函數建立get_sza()
函數,其會回傳一個Polars DataFrame。由於在此app
中,DataFrame並不會變動,所以這邊可以使用@cache裝飾在get_sza()
之上。
# https://github.com/jrycw/gt-fasthtml/blob/master/main.py
@cache
def get_sza():
return pl.from_pandas(sza)
get()
函數使用@rt("/")
來定義get()
,作為使用者以HTTP GET
訪問「"/"」時所使用的函數。
# https://github.com/jrycw/gt-fasthtml/blob/master/main.py
@rt("/")
def get():
sza_pivot = (
get_sza()
.filter((pl.col("latitude") == "20") & (pl.col("tst") <= "1200"))
.select(pl.col("*").exclude("latitude"))
.drop_nulls()
.pivot(values="sza", index="month", on="tst", sort_columns=True)
)
sza_gt = (
GT(sza_pivot, rowname_col="month")
.data_color(
domain=[90, 0],
palette=["rebeccapurple", "white", "orange"],
na_color="white",
)
.tab_header(
title="Solar Zenith Angles from 05:30 to 12:00",
subtitle=html("Average monthly values at latitude of 20°N."),
)
.sub_missing(missing_text="")
)
return (
Title("FastHTML-GT Website"),
H1("Great Tables shown in FastHTML", style="text-align:center"),
NotStr(sza_gt.as_raw_html())
)
分段說明如下:
sza_pivot
為Polars DataFrame的整理過程;sza_gt
則為表格的製作過程。Title
對應HTML中的title
tag。Title("FastHTML-GT Website")
相當於將title
tag設為「"FastHTML-GT Website"」。H1("Great Tables shown in FastHTML", style="text-align:center")
相當於將h1
tag設為「"Great Tables shown in FastHTML"」以及增加「"text-align:center"」至CSS中。這樣的寫法對於年輕的朋友可能會有點不習慣,像是在寫FastAPI,卻要在不使用templates.TemplateResponse
的情況下回傳HTML,而不是JSON。
但是依稀記得在千禧年前後,那段還是用FrontPage
或是Dreamweaver
架站的時代,接收HTML為回傳值不是很正常的嗎?:)
於命令列中執行下列指令:
uvicorn main:app --reload
接著打開瀏覽器前往預設網址,如http://127.0.0.1:8000/,就可以見到下面這個漂亮的表格:
我們希望能夠編寫一個裝飾器來簡化NotStr(sza_gt.as_raw_html())
,目標是使其能夠裝飾於一個回傳GT
instance的函數上。實作程式碼如下:
# https://github.com/jrycw/ft-gt-demo/blob/master/ft_gt/__init__.py
from functools import partial, wraps
from fasthtml.common import Div, NotStr
def gt2fasthtml(func=None, **div_kwargs):
"""
https://pybit.es/articles/decorator-optional-argument/
"""
if func is None:
return partial(gt2fasthtml, **div_kwargs)
@wraps(func)
def wrapper(*args, **kwargs):
gtbl = func(*args, **kwargs)
gtbl_html = gtbl.as_raw_html()
return Div(NotStr(gtbl_html), **div_kwargs)
return wrapper
一開頭我們以func
是不是None
來判斷該裝飾器是以@gt2fasthtml
或是@gt2fasthtml()
的方式來呼叫。這兩種呼叫方式將是等義的,是種增加使用者體驗的設計。
接著在wrapper()
函數內:
func(*args, **kwargs)
取得所裝飾函數的結果,即一個GT
instance。GT.as_raw_html()
取得表格的HTML格式。NotStr
包裹後,再使用Div
包裹。這邊請留意,我們將允許使用者自gt2fasthtml()
來傳入Div
的屬性。例如@gt2fasthtml(id="gt")
,就代表將id="gt"
傳入給Div
。如果對裝飾器這個概念仍然不是那麼清楚的朋友,可以參考我於2023年鐵人賽的系列文 ── Python十翼:與未來的自己對話,其中有詳細的說明。
我們可以編寫測試,來看看裝飾器是否正常運作,有興趣的朋友可以參考下列程式碼,了解其五種使用情境。
# https://github.com/jrycw/ft-gt-demo/blob/master/tests/test_basic.py
import fastcore
from ft_gt import gt2fasthtml
def test_dec_without_parentheses(gtbl):
@gt2fasthtml # gt2fasthtml, without parentheses
def get_gtbl():
return gtbl
div_comp = get_gtbl()
assert isinstance(div_comp, fastcore.xml.FT)
def test_dec_with_parentheses(gtbl):
@gt2fasthtml() # gt2fasthtml(), with parentheses
def get_gtbl():
return gtbl
div_comp = get_gtbl()
assert isinstance(div_comp, fastcore.xml.FT)
def test_dec_with_div_kwargs(gtbl):
@gt2fasthtml(id="gt")
def get_gtbl():
return gtbl
div_comp = get_gtbl()
assert isinstance(div_comp, fastcore.xml.FT)
assert div_comp.attrs["id"] == "gt"
def test_func_with_parentheses(gtbl):
def get_gtbl():
return gtbl
get_gtbl = gt2fasthtml(get_gtbl)
div_comp = get_gtbl()
assert isinstance(div_comp, fastcore.xml.FT)
def test_func_with_div_kwargs(gtbl):
def get_gtbl():
return gtbl
get_gtbl = gt2fasthtml(get_gtbl, id="gt")
div_comp = get_gtbl()
assert isinstance(div_comp, fastcore.xml.FT)
assert div_comp.attrs["id"] == "gt"
靜態表格可參考gt-fasthtml repo,裝飾器及其測試則可參考ft-gt-demo repo。